/*
Copyright (c) 2014 Sanjay Dasgupta (sanjay.dasgupta@gmail.com)
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package ipex

import scala.swing._
import scala.collection.mutable
import scala.swing.Swing._
import scala.swing.event._
import scala.util.Properties
import javax.swing.{ImageIcon, UIManager}
import scala.swing.TabbedPane.Page
import java.awt.{Color, Toolkit}
import scala.Some
import javax.swing.border.LineBorder
import scala.io.Source
import scala.xml.XML

class Main extends MainFrame {

  private def setupGui() {
    title = Main.titlePrefix
    selectedInfoArea.editable = false
    selectedInfoArea.contentType = "text/html"
    relatedInfoArea.editable = false
    relatedInfoArea.contentType = "text/html"
    contents = new BorderPanel {
      add(searchPanel, BorderPanel.Position.West)
      add(resultsPanel, BorderPanel.Position.Center)
    }
    val screenSize = Toolkit.getDefaultToolkit.getScreenSize
    val width = (screenSize.width * 0.8).asInstanceOf[Int]
    val height = (screenSize.height * 0.8).asInstanceOf[Int]
    size = (width, height)
    centerOnScreen()
    licensePanel.text = Main.license
    visible = true
  }

  private def search() {
    val searchText = searchField.text.trim.toLowerCase
    if (searchText.isEmpty) {
      Main.actResultsList.listData = MemData.sectionNumbers.filter(!_.contains("(")).
        map(s => s"$s. ${MemData.sectionTitles(s)}")
      Main.rulesResultsList.listData = MemData.ruleNumbers.filter(!_.contains("(")).
        map(s => s"$s. ${MemData.ruleTitles(s)}")
      displayForms(((1 to 7) ++ Seq("7A") ++ (8 to 28)).map(_.toString))
    } else {
      val sectionData = MemData.searchSections(searchText).map(s => s"${s.n}. ${MemData.sectionTitles(s.n)}")
      Main.actResultsList.listData = sectionData
      val ruleData = MemData.searchRules(searchText).map(r => s"${r.n}. ${MemData.ruleTitles(r.n)}")
      Main.rulesResultsList.listData = ruleData
      Main.displayForms(Nil)
      if (sectionData.isEmpty && ruleData.isEmpty) {
        Dialog.showMessage(contents.head, s"No matches found for '${searchField.text}'", "Search failed", Dialog.Message.Plain)
      }
    }
  }

  def highlight(txt: String, wordToHighlight: String): String = {
    val lTxt = txt.toLowerCase
    val toHighlight = wordToHighlight.toLowerCase
    if (!toHighlight.isEmpty && lTxt.contains(toHighlight)) {
      val i = lTxt.indexOf(toHighlight)
      txt.substring(0, i) + s"<font bgcolor='#FF6600'>${txt.substring(i, i + wordToHighlight.length)}</font>" +
        highlight(txt.substring(i + wordToHighlight.length), wordToHighlight)
    } else
      txt
  }

  private def baseNumber(nbr: String) = if (nbr.contains('(')) nbr.substring(0, nbr.indexOf('(')) else nbr

  private def displayRelatedItems(isRule: Boolean, number: String) {
    val relatedNumbers: Seq[String] = isRule match {
      case true => MemData.relatedSectionNumbers(number)
      case false => MemData.relatedRuleNumbers(number)
    }
    val baseRelatedNumbers = relatedNumbers.map(baseNumber)
    val items = isRule match {
      case true => baseRelatedNumbers.flatMap(MemData.sectionsTree).distinct
      case false => baseRelatedNumbers.flatMap(MemData.rulesTree).distinct
    }
    val text = items map {
      case K(nbr, Some(ttl), Some(txt), _) => Pair(nbr, s"<a name='$nbr'>$nbr</a> <b>$ttl</b>$txt")
      case K(nbr, None, Some(txt), _) => Pair(nbr, s"<a name='$nbr'>$nbr</a> $txt")
      case K(nbr, Some(ttl), None, _) => Pair(nbr, s"<a name='$nbr'>$nbr</a> <b>$ttl</b>")
    } map (p => if (relatedNumbers.contains(p._1) && items.size > 1)
      s"<p><font bgcolor='yellow'>${p._2}</font></p>" else s"<p>${p._2}</p>") mkString ("<html>", "\n", "</html>")
    if (text == "<html></html>") {
      resultsTabbedPane.pages(1).enabled = false
    } else {
      resultsTabbedPane.pages(1).enabled = true
      relatedInfoArea.text = text
      relatedInfoArea.peer.scrollToReference(relatedNumbers.head)
    }
    val relNbrs = relatedNumbers.sorted.mkString(", ")
    relatedInfoLabel.text = if (isRule) s"Section(s) $relNbrs" else s"Rule(s) $relNbrs"
    if (!isRule)
      displayForms((relatedNumbers.flatMap(MemData.ancestors) ++ relatedNumbers.flatMap(MemData.ruleDescendants)).
        flatMap(MemData.ruleToForms))
  }

  private def comments(isRule: Boolean, number: String): String = {
    isRule match {
      case true =>
        MemData.ancestors(number).flatMap(MemData.ruleToLabels).distinct.mkString(" | ") match {
         case "" => ""
          case labels => s"<font color='#0000ff'>[<i>Groups: $labels</i>]</font>"
        }
      case false =>
        MemData.ancestors(number).flatMap(MemData.sectionToLabels).distinct.mkString(" | ") match {
          case "" => ""
          case labels => s"<font color='#0000ff'>[<i>Groups: $labels</i>]</font>"
        }
    }
  }

  private def displaySelectedItem(isRule: Boolean, number: String, hightlight: String) {
    selectedInfoLabel.text = if (isRule) s"Rule $number" else s"Section $number"
    val baseNbr = baseNumber(number)
    val items = isRule match {
      case true => MemData.rulesTree(baseNbr)
      case false => MemData.sectionsTree(baseNbr)
    }
    val text = items map {
      case K(nbr, Some(ttl), Some(txt), _) if nbr == number => Pair(nbr, highlight(s"<a name='$nbr'>$nbr</a> <b>$ttl</b>$txt", hightlight))
      case K(nbr, Some(ttl), Some(txt), _) => Pair(nbr, s"<a name='$nbr'>$nbr</a> <b>$ttl</b>$txt")
      case K(nbr, None, Some(txt), _) if nbr == number => Pair(nbr, highlight(s"<a name='$nbr'>$nbr</a> $txt", hightlight))
      case K(nbr, None, Some(txt), _) => Pair(nbr, s"<a name='$nbr'>$nbr</a> $txt")
      case K(nbr, Some(ttl), None, _) if nbr == number => Pair(nbr, highlight(s"<a name='$nbr'>$nbr</a> <b>$ttl</b>", hightlight))
      case K(nbr, Some(ttl), None, _) => Pair(nbr, s"<a name='$nbr'>$nbr</a> <b>$ttl</b>")
    } map (p => if (p._1 == number && items.size > 1)
      s"<p><font bgcolor='yellow'>${p._2}</font> ${comments(isRule, p._1)}</p>" else
      s"<p>${p._2} ${comments(isRule, p._1)}</p>") mkString("<html>", "\n", "</html>")
    selectedInfoArea.text = text
    selectedInfoArea.peer.scrollToReference(number)
    displayRelatedItems(isRule, number)
    resultsTabbedPane.selection.index = 0
    if (isRule)
      displayForms((MemData.ancestors(number) ++ MemData.ruleDescendants(number)).flatMap(MemData.ruleToForms))
  }

  private def displayForms(formNos: Seq[String]) {
    val twoPart = Seq("1", "6", "12", "22", "25")
    val allForms: Seq[String] = formNos.flatMap(n => if (twoPart.contains(n)) Seq(s"$n-1", s"$n-2") else Seq(n)).distinct
    val images = allForms.map(fn => new ImageIcon(ClassLoader.getSystemResource(s"resources/form-$fn.png")))
    val labels = images.map(i => {val l = new Label; l.icon = i; l.border = new LineBorder(Color.black); l})
    if (labels.nonEmpty) {
      val grid = new GridPanel(labels.size, 1) {
        contents ++= labels
      }
      formsPage.content.asInstanceOf[ScrollPane].viewportView = grid
    } else
      formsPage.content.asInstanceOf[ScrollPane].viewportView = None
    formsPage.enabled = labels.nonEmpty
  }

  def initializeData() {
    MemData.init()
    search()
  }
  
  val searchField = new TextField()
  searchField.action = Action(""){search(); Main.title(s"keyword: ${searchField.text}")}
  val searchButton = Button("Search"){search(); Main.title(s"keyword: ${searchField.text}")}
  val clearButton = Button("ALL"){searchField.text = ""; search(); Main.title("All")}
  def xstring(x: String) = if (x.startsWith("<html>")) XML.loadString(x).text else x
  Main.actResultsList.selection.reactions += {case ListSelectionChanged(_, _, true) =>
    Main.rulesResultsList.selectIndices()
    displaySelectedItem(isRule = false, xstring(Main.actResultsList.selection.items.head).split("\\.").head, searchField.text.trim)}
  Main.rulesResultsList.selection.reactions += {case ListSelectionChanged(_, _, true) =>
    Main.actResultsList.selectIndices()
    displaySelectedItem(isRule = true, xstring(Main.rulesResultsList.selection.items.head).split("\\.").head, searchField.text.trim)}
  val searchPanel = new BorderPanel {
    add(new Label("Locate / Search"), BorderPanel.Position.North)
    add(AreaMap, BorderPanel.Position.Center)
    val buttonPanel = new GridPanel(3, 1) {
      border = new LineBorder(Color.gray)
      val buttons = new GridPanel(1, 2) {
        contents += (searchButton, clearButton)
      }
      contents += (new Label("Keyword Search"), searchField, buttons)
    }
    add(buttonPanel, BorderPanel.Position.South)
    border = new LineBorder(Color.gray)
  }

  val selectedInfoArea = new EditorPane()
  val selectedInfoLabel = new Label("-")
  val selectedInfoPanel = new BorderPanel {
    add(selectedInfoLabel, BorderPanel.Position.North)
    add(new ScrollPane(selectedInfoArea), BorderPanel.Position.Center)
  }
  val selectedInfoPage = new Page("Selected Info", selectedInfoPanel)
  
  val relatedInfoArea = new EditorPane()
  val relatedInfoLabel = new Label("-")
  val relatedInfoPanel = new BorderPanel {
    add(relatedInfoLabel, BorderPanel.Position.North)
    add(new ScrollPane(relatedInfoArea), BorderPanel.Position.Center)
  }
  val relatedInfoPage = new Page("Related Info", relatedInfoPanel)
  val licensePanel = new EditorPane()
  licensePanel.contentType = "text/html"
  val licenseScrollPane = new ScrollPane(licensePanel)
  licenseScrollPane.horizontalScrollBarPolicy = ScrollPane.BarPolicy.Never
  licenseScrollPane.verticalScrollBarPolicy = ScrollPane.BarPolicy.AsNeeded
  val licensePage = new Page("License", licenseScrollPane)
  
  val formsPage = new Page("Forms", new ScrollPane())
  val resultsTabbedPane = new TabbedPane {
    //tabPlacement = Alignment.Bottom
    relatedInfoPage.enabled = false
    pages += (selectedInfoPage, relatedInfoPage, formsPage, licensePage)
    formsPage.enabled = false
  }
  val resultsPanel = new BorderPanel {
    add(resultsTabbedPane, BorderPanel.Position.Center)
    val searchResultsPanel = new GridPanel(1, 2) {
      val a = new BorderPanel {
        add(new Label("Sections"), BorderPanel.Position.North)
        add(new ScrollPane(Main.actResultsList), BorderPanel.Position.Center)
      }
      val r = new BorderPanel {
        add(new Label("Rules"), BorderPanel.Position.North)
        add(new ScrollPane(Main.rulesResultsList), BorderPanel.Position.Center)
      }
      contents += (a, r)
    }
    add(searchResultsPanel, BorderPanel.Position.South)
  }
  setupGui()
}

object Main extends SimpleSwingApplication {
  try {
    if (Properties.osName.contains("indows"))
      UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel")
    else
      UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName)
  } catch {
    case e: Throwable => Dialog.showMessage(null, e.getMessage, "Info")
  }
  val license = Source.fromInputStream(ClassLoader.getSystemResourceAsStream("resources/license.html")).
    getLines().toSeq.mkString("\n")
  val actResultsList = new ListView[String]()
  val rulesResultsList = new ListView[String]()
  val titlePrefix = "Indian-Patent-Explorer-0.94"
  val menuItemsMap = mutable.Map[String, MenuItem]()
  val toolItemsMap = mutable.Map[String, Button]()
  val top = new Main
  top.initializeData()
  def title(t: String) {
    top.title = s"$titlePrefix ($t)"
  }
  def searchSuccessful() {
    top.selectedInfoArea.text = ""
    top.selectedInfoLabel.text = "-"
    top.relatedInfoArea.text = ""
    top.relatedInfoLabel.text = "-"
    top.resultsTabbedPane.selection.index = 0
  }
  def clearSearchText() {top.searchField.text = ""}
  def displayForms(formNos: Seq[String]) {top.displayForms(formNos)}
}

